#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest/doctest.h"

#include "klib/objpool.hpp"
#include <cassert>

using namespace klib;
using namespace std::string_literals;
using namespace std::string_view_literals;

TEST_CASE("objpool") {

    SUBCASE("objpool") {
        struct A {
            uint8_t u8;
            uint32_t u32;
            uint64_t u64;
            A() {}
            A(uint8_t u8, uint32_t u32, uint64_t u64): u8(u8), u32(u32), u64(u64) {}
            ~A() {}
        };

        const size_t A_SIZE = 128;
        A* ps[A_SIZE], *p1, *p2, *p3;
        obj_pool_t<A, A_SIZE> pool;

        for (size_t i = 0; i < A_SIZE; ++i) {
            ps[i] = pool.malloc(i, i, i);
        }
        p1 = pool.malloc(1, 2, 3);
        p2 = pool.malloc(4, 5, 6);
        p3 = pool.malloc();

        for (size_t i = 0; i < A_SIZE; ++i) {
            INFO("check value, i = " << i);
            REQUIRE_EQ(i, ps[i]->u8);
            REQUIRE_EQ(i, ps[i]->u32);
            REQUIRE_EQ(i, ps[i]->u64);
        }
        
        for (size_t i = 1; i < A_SIZE; ++i) {
            INFO("check address, i = " << i);
            REQUIRE_EQ(ps[0] + i, ps[i]);
        }
        REQUIRE_NE(ps[0] + A_SIZE + 0, p1);
        REQUIRE_NE(ps[0] + A_SIZE + 1, p2);
        REQUIRE_NE(ps[0] + A_SIZE + 2, p3);

        const size_t idxes[] = {3, 7, 8, 78, 79, 80, 121, 123};
        for (auto i = 0ull; i < std::size(idxes); ++i) {
            REQUIRE_LE(idxes[i], A_SIZE);
            pool.free(ps[idxes[i]]);
        }
        pool.free(p1);

        for (auto i = 0ull; i < std::size(idxes); ++i) {
            auto v = idxes[i];
            auto a = pool.malloc((uint8_t)v, (uint32_t)v, (uint64_t)v);
            REQUIRE_EQ(ps[v], a);
        }
        p1 = pool.malloc(1, 2, 3);
        bool b = p1 < ps[0] || p1 > ps[A_SIZE - 1];
        REQUIRE_MESSAGE(b, "alloc error");
    }

    SUBCASE("new/delete") {
        static mem_pool_t<8, 64> gpool;
        struct A {
            int n;
            A(int n): n(n) { std::printf("call A(%d)\n", n); }
            ~A() { std::printf("call ~A(), n = %d\n", n); }

            void* operator new(size_t size) {
                auto p = gpool.malloc();
                std::printf("call new A, return: %p\n", p);
                return p;
            }

            void operator delete(void* ptr) {
                std::printf("call delete A, param: %p\n", ptr);
                gpool.free(ptr);
            }
        };

        A* a = new A(102);
        std::printf("a address: %p\n", a);
        REQUIRE_EQ(a->n, 102);
        delete a;
    }
}